; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --version 2
; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal  -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=3 -S < %s | FileCheck %s --check-prefixes=CHECK,TUNIT
; RUN: opt -aa-pipeline=basic-aa -passes=attributor-cgscc -attributor-manifest-internal  -attributor-annotate-decl-cs -S < %s | FileCheck %s --check-prefixes=CHECK,CGSCC

declare nofpclass(nan) float @ret_nofpclass_nan()
declare [2 x [3 x float]] @ret_array()
declare float @extern()
declare void @extern.use(float)
declare void @extern.use.array([2 x [3 x float]])
declare void @llvm.assume(i1 noundef)
declare void @unknown()
declare half @llvm.fabs.f16(half)
declare void @extern.use.f16(half)
declare i1 @llvm.is.fpclass.f32(float, i32 immarg)

define float @returned_0() {
; CHECK-LABEL: define noundef nofpclass(nan inf nzero sub norm) float @returned_0() {
; CHECK-NEXT:    call void @unknown()
; CHECK-NEXT:    ret float 0.000000e+00
;
  call void @unknown()
  ret float 0.0
}

define float @returned_neg0() {
; CHECK-LABEL: define noundef nofpclass(nan inf pzero sub norm) float @returned_neg0() {
; CHECK-NEXT:    call void @unknown()
; CHECK-NEXT:    ret float -0.000000e+00
;
  call void @unknown()
  ret float -0.0
}

define float @returned_undef() {
; CHECK-LABEL: define nofpclass(all) float @returned_undef() {
; CHECK-NEXT:    call void @unknown()
; CHECK-NEXT:    ret float undef
;
  call void @unknown()
  ret float undef
}

define float @returned_poison() {
; CHECK-LABEL: define nofpclass(all) float @returned_poison() {
; CHECK-NEXT:    call void @unknown()
; CHECK-NEXT:    ret float poison
;
  call void @unknown()
  ret float poison
}

define double @returned_snan() {
; CHECK-LABEL: define noundef nofpclass(qnan inf zero sub norm) double @returned_snan() {
; CHECK-NEXT:    call void @unknown()
; CHECK-NEXT:    ret double 0x7FF0000000000001
;
  call void @unknown()
  ret double 0x7FF0000000000001
}

define double @returned_qnan() {
; CHECK-LABEL: define noundef nofpclass(snan inf zero sub norm) double @returned_qnan() {
; CHECK-NEXT:    call void @unknown()
; CHECK-NEXT:    ret double 0x7FF8000000000000
;
  call void @unknown()
  ret double 0x7FF8000000000000
}

define <2 x double> @returned_zero_vector() {
; CHECK-LABEL: define noundef nofpclass(nan inf nzero sub norm) <2 x double> @returned_zero_vector() {
; CHECK-NEXT:    call void @unknown()
; CHECK-NEXT:    ret <2 x double> zeroinitializer
;
  call void @unknown()
  ret <2 x double> zeroinitializer
}

define <2 x double> @returned_negzero_vector() {
; CHECK-LABEL: define noundef nofpclass(nan inf pzero sub norm) <2 x double> @returned_negzero_vector() {
; CHECK-NEXT:    call void @unknown()
; CHECK-NEXT:    ret <2 x double> <double -0.000000e+00, double -0.000000e+00>
;
  call void @unknown()
  ret <2 x double> <double -0.0, double -0.0>
}

define <2 x double> @returned_qnan_zero_vector() {
; CHECK-LABEL: define noundef <2 x double> @returned_qnan_zero_vector() {
; CHECK-NEXT:    call void @unknown()
; CHECK-NEXT:    ret <2 x double> <double 0x7FF8000000000000, double 0.000000e+00>
;
  call void @unknown()
  ret <2 x double> <double 0x7FF8000000000000, double 0.0>
}

; Return a float trivially nofpclass(nan) (call return attribute)
define float @return_nofpclass_nan_decl_return() {
; CHECK-LABEL: define nofpclass(nan) float @return_nofpclass_nan_decl_return() {
; CHECK-NEXT:    [[RET:%.*]] = call nofpclass(nan) float @ret_nofpclass_nan()
; CHECK-NEXT:    ret float [[RET]]
;
  %ret = call float @ret_nofpclass_nan()
  ret float %ret
}

; Return a float trivially nofpclass(nan) (argument attribute)
define float @return_nofpclass_nan_arg(float returned nofpclass(nan) %p) {
; CHECK: Function Attrs: nofree norecurse nosync nounwind willreturn memory(none)
; CHECK-LABEL: define nofpclass(nan) float @return_nofpclass_nan_arg
; CHECK-SAME: (float returned nofpclass(nan) [[P:%.*]]) #[[ATTR2:[0-9]+]] {
; CHECK-NEXT:    ret float [[P]]
;
  ret float %p
}

define [2 x [3 x float]] @return_nofpclass_inf_ret_array() {
; CHECK-LABEL: define nofpclass(inf) [2 x [3 x float]] @return_nofpclass_inf_ret_array() {
; CHECK-NEXT:    [[RET:%.*]] = call nofpclass(inf) [2 x [3 x float]] @ret_array()
; CHECK-NEXT:    ret [2 x [3 x float]] [[RET]]
;
  %ret = call nofpclass(inf) [2 x [3 x float]]  @ret_array()
  ret [2 x [3 x float]] %ret
}

define float @returned_nnan_fadd(float %arg0, float %arg1) {
; CHECK: Function Attrs: nofree norecurse nosync nounwind willreturn memory(none)
; CHECK-LABEL: define nofpclass(nan) float @returned_nnan_fadd
; CHECK-SAME: (float [[ARG0:%.*]], float [[ARG1:%.*]]) #[[ATTR2]] {
; CHECK-NEXT:    [[FADD:%.*]] = fadd nnan float [[ARG0]], [[ARG1]]
; CHECK-NEXT:    ret float [[FADD]]
;
  %fadd = fadd nnan float %arg0, %arg1
  ret float %fadd
}

define float @return_nofpclass_nan_callsite() {
; CHECK-LABEL: define nofpclass(nan) float @return_nofpclass_nan_callsite() {
; CHECK-NEXT:    [[CALL:%.*]] = call nofpclass(nan) float @extern()
; CHECK-NEXT:    ret float [[CALL]]
;
  %call = call nofpclass(nan) float @extern()
  ret float %call
}

; Can union the return classes
define nofpclass(inf) float @return_ninf_nofpclass_nan_callsite() {
; CHECK-LABEL: define nofpclass(nan inf) float @return_ninf_nofpclass_nan_callsite() {
; CHECK-NEXT:    [[CALL:%.*]] = call nofpclass(nan) float @extern()
; CHECK-NEXT:    ret float [[CALL]]
;
  %call = call nofpclass(nan) float @extern()
  ret float %call
}

define void @arg_used_by_nofpclass_nan_callsite(float %arg) {
; CHECK-LABEL: define void @arg_used_by_nofpclass_nan_callsite
; CHECK-SAME: (float [[ARG:%.*]]) {
; CHECK-NEXT:    call void @extern.use(float nofpclass(nan) [[ARG]])
; CHECK-NEXT:    ret void
;
  call void @extern.use(float nofpclass(nan) %arg)
  ret void
}

; Callsite can union the incoming and outgoing
define void @ninf_arg_used_by_nofpclass_nan_callsite(float nofpclass(inf) %arg) {
; CHECK-LABEL: define void @ninf_arg_used_by_nofpclass_nan_callsite
; CHECK-SAME: (float nofpclass(inf) [[ARG:%.*]]) {
; CHECK-NEXT:    call void @extern.use(float nofpclass(nan inf) [[ARG]])
; CHECK-NEXT:    ret void
;
  call void @extern.use(float nofpclass(nan) %arg)
  ret void
}

define void @ninf_arg_used_by_callsite_array([2 x [3 x float]] nofpclass(inf) %arg) {
; CHECK-LABEL: define void @ninf_arg_used_by_callsite_array
; CHECK-SAME: ([2 x [3 x float]] nofpclass(inf) [[ARG:%.*]]) {
; CHECK-NEXT:    call void @extern.use.array([2 x [3 x float]] nofpclass(inf) [[ARG]])
; CHECK-NEXT:    ret void
;
  call void @extern.use.array([2 x [3 x float]]  %arg)
  ret void
}

define void @nofpclass_call_use_after_unannotated_use(float %arg) {
; CHECK-LABEL: define void @nofpclass_call_use_after_unannotated_use
; CHECK-SAME: (float [[ARG:%.*]]) {
; CHECK-NEXT:    call void @extern(float [[ARG]]) #[[ATTR5:[0-9]+]]
; CHECK-NEXT:    call void @extern(float nofpclass(nan inf) [[ARG]])
; CHECK-NEXT:    ret void
;
  call void @extern(float %arg) willreturn nounwind ; < annotate this use
  call void @extern(float nofpclass(nan inf) %arg)
  ret void
}

define float @mutually_recursive0(float %arg) {
; TUNIT: Function Attrs: nofree nosync nounwind willreturn memory(none)
; TUNIT-LABEL: define nofpclass(all) float @mutually_recursive0
; TUNIT-SAME: (float [[ARG:%.*]]) #[[ATTR3:[0-9]+]] {
; TUNIT-NEXT:    ret float undef
;
; CGSCC: Function Attrs: nofree norecurse nosync nounwind willreturn memory(none)
; CGSCC-LABEL: define nofpclass(all) float @mutually_recursive0
; CGSCC-SAME: (float [[ARG:%.*]]) #[[ATTR2]] {
; CGSCC-NEXT:    ret float undef
;
  %call = call float @mutually_recursive1(float %arg)
  ret float %call
}

define float @mutually_recursive1(float %arg) {
; TUNIT: Function Attrs: nofree nosync nounwind willreturn memory(none)
; TUNIT-LABEL: define nofpclass(all) float @mutually_recursive1
; TUNIT-SAME: (float [[ARG:%.*]]) #[[ATTR3]] {
; TUNIT-NEXT:    ret float undef
;
; CGSCC: Function Attrs: nofree norecurse nosync nounwind willreturn memory(none)
; CGSCC-LABEL: define nofpclass(all) float @mutually_recursive1
; CGSCC-SAME: (float [[ARG:%.*]]) #[[ATTR2]] {
; CGSCC-NEXT:    ret float undef
;
  %call = call float @mutually_recursive0(float %arg)
  ret float %call
}

define float @recursive_phi(ptr %ptr) {
; CHECK-LABEL: define nofpclass(nan) float @recursive_phi
; CHECK-SAME: (ptr nofree [[PTR:%.*]]) {
; CHECK-NEXT:  entry:
; CHECK-NEXT:    [[RET:%.*]] = call nofpclass(nan) float @ret_nofpclass_nan()
; CHECK-NEXT:    br label [[LOOP:%.*]]
; CHECK:       loop:
; CHECK-NEXT:    [[PHI:%.*]] = phi float [ [[RET]], [[ENTRY:%.*]] ], [ [[RET]], [[LOOP]] ]
; CHECK-NEXT:    [[COND:%.*]] = load volatile i1, ptr [[PTR]], align 1
; CHECK-NEXT:    br i1 [[COND]], label [[LOOP]], label [[EXIT:%.*]]
; CHECK:       exit:
; CHECK-NEXT:    ret float [[RET]]
;
entry:
  %ret = call float @ret_nofpclass_nan()
  br label %loop

loop:
  %phi = phi float [%ret, %entry], [%phi, %loop]
  %cond = load volatile i1, ptr %ptr
  br i1 %cond, label %loop, label %exit

exit:
  ret float %phi
}

; Should be able to infer nofpclass(nan) return
define float @fcmp_uno_check(float %arg) local_unnamed_addr {
; CHECK-LABEL: define float @fcmp_uno_check
; CHECK-SAME: (float [[ARG:%.*]]) local_unnamed_addr {
; CHECK-NEXT:  entry:
; CHECK-NEXT:    [[ISNAN:%.*]] = fcmp uno float [[ARG]], 0.000000e+00
; CHECK-NEXT:    br i1 [[ISNAN]], label [[BB0:%.*]], label [[BB1:%.*]]
; CHECK:       bb0:
; CHECK-NEXT:    [[CALL:%.*]] = call nofpclass(nan) float @ret_nofpclass_nan()
; CHECK-NEXT:    br label [[BB1]]
; CHECK:       bb1:
; CHECK-NEXT:    [[PHI:%.*]] = phi float [ [[CALL]], [[BB0]] ], [ [[ARG]], [[ENTRY:%.*]] ]
; CHECK-NEXT:    ret float [[PHI]]
;
entry:
  %isnan = fcmp uno float %arg, 0.0
  br i1 %isnan, label %bb0, label %bb1

bb0:
  %call = call float @ret_nofpclass_nan()
  br label %bb1

bb1:
  %phi = phi float [ %call, %bb0 ], [ %arg, %entry ]
  ret float %phi
}

; Should be able to infer nofpclass(nan) on %arg use
define void @fcmp_ord_guard_callsite_arg(float %arg) {
; CHECK-LABEL: define void @fcmp_ord_guard_callsite_arg
; CHECK-SAME: (float [[ARG:%.*]]) {
; CHECK-NEXT:  entry:
; CHECK-NEXT:    [[IS_NOT_NAN:%.*]] = fcmp ord float [[ARG]], 0.000000e+00
; CHECK-NEXT:    br i1 [[IS_NOT_NAN]], label [[BB0:%.*]], label [[BB1:%.*]]
; CHECK:       bb0:
; CHECK-NEXT:    call void @extern.use(float [[ARG]])
; CHECK-NEXT:    br label [[BB1]]
; CHECK:       bb1:
; CHECK-NEXT:    ret void
;
entry:
  %is.not.nan = fcmp ord float %arg, 0.0
  br i1 %is.not.nan, label %bb0, label %bb1

bb0:
  call void @extern.use(float %arg)
  br label %bb1

bb1:
  ret void
}

; Should be able to infer nofpclass on both %arg uses
define float @fcmp_ord_assume_callsite_arg_return(float %arg) {
; CHECK-LABEL: define nofpclass(inf zero sub norm) float @fcmp_ord_assume_callsite_arg_return
; CHECK-SAME: (float returned nofpclass(inf zero sub norm) [[ARG:%.*]]) {
; CHECK-NEXT:  entry:
; CHECK-NEXT:    [[IS_NOT_NAN:%.*]] = fcmp ord float [[ARG]], 0.000000e+00
; CHECK-NEXT:    call void @llvm.assume(i1 noundef [[IS_NOT_NAN]]) #[[ATTR6:[0-9]+]]
; CHECK-NEXT:    call void @extern.use(float nofpclass(inf zero sub norm) [[ARG]])
; CHECK-NEXT:    ret float [[ARG]]
;
entry:
  %is.not.nan = fcmp ord float %arg, 0.0
  call void @llvm.assume(i1 %is.not.nan)
  call void @extern.use(float %arg)
  ret float %arg
}

define internal float @returned_dead() {
; CHECK-LABEL: define internal nofpclass(nan inf nzero sub norm) float @returned_dead() {
; CHECK-NEXT:    call void @unknown()
; CHECK-NEXT:    ret float undef
;
  call void @unknown()
  ret float 0.0
}

define void @returned_dead_caller() {
; CHECK-LABEL: define void @returned_dead_caller() {
; CHECK-NEXT:    [[TMP1:%.*]] = call float @returned_dead()
; CHECK-NEXT:    ret void
;
  call float @returned_dead()
  ret void
}

define internal float @only_nofpclass_inf_callers(float %arg) {
; CHECK: Function Attrs: nofree norecurse nosync nounwind willreturn memory(none)
; CHECK-LABEL: define internal float @only_nofpclass_inf_callers
; CHECK-SAME: (float nofpclass(inf) [[ARG:%.*]]) #[[ATTR2]] {
; CHECK-NEXT:    [[ADD:%.*]] = fadd float [[ARG]], [[ARG]]
; CHECK-NEXT:    ret float [[ADD]]
;
  %add = fadd float %arg, %arg
  ret float %add
}

define float @call_noinf_0(float nofpclass(inf) %arg) {
; TUNIT: Function Attrs: nofree norecurse nosync nounwind willreturn memory(none)
; TUNIT-LABEL: define float @call_noinf_0
; TUNIT-SAME: (float nofpclass(inf) [[ARG:%.*]]) #[[ATTR2]] {
; TUNIT-NEXT:    [[RESULT:%.*]] = call float @only_nofpclass_inf_callers(float nofpclass(inf) [[ARG]]) #[[ATTR7:[0-9]+]]
; TUNIT-NEXT:    ret float [[RESULT]]
;
; CGSCC: Function Attrs: nofree nosync nounwind willreturn memory(none)
; CGSCC-LABEL: define float @call_noinf_0
; CGSCC-SAME: (float nofpclass(inf) [[ARG:%.*]]) #[[ATTR3:[0-9]+]] {
; CGSCC-NEXT:    [[RESULT:%.*]] = call float @only_nofpclass_inf_callers(float nofpclass(inf) [[ARG]]) #[[ATTR6]]
; CGSCC-NEXT:    ret float [[RESULT]]
;
  %result = call float @only_nofpclass_inf_callers(float %arg)
  ret float %result
}

define float @call_noinf_1(float %arg) {
; TUNIT: Function Attrs: nofree norecurse nosync nounwind willreturn memory(none)
; TUNIT-LABEL: define float @call_noinf_1
; TUNIT-SAME: (float [[ARG:%.*]]) #[[ATTR2]] {
; TUNIT-NEXT:    [[RESULT:%.*]] = call float @only_nofpclass_inf_callers(float nofpclass(inf) [[ARG]]) #[[ATTR7]]
; TUNIT-NEXT:    ret float [[RESULT]]
;
; CGSCC: Function Attrs: nofree nosync nounwind willreturn memory(none)
; CGSCC-LABEL: define float @call_noinf_1
; CGSCC-SAME: (float [[ARG:%.*]]) #[[ATTR3]] {
; CGSCC-NEXT:    [[RESULT:%.*]] = call float @only_nofpclass_inf_callers(float nofpclass(inf) [[ARG]]) #[[ATTR6]]
; CGSCC-NEXT:    ret float [[RESULT]]
;
  %result = call float @only_nofpclass_inf_callers(float nofpclass(inf) %arg)
  ret float %result
}

; TODO: Should be able to infer nofpclass(inf) on return
define internal float @only_nofpclass_inf_return_users(float %arg) {
; CHECK: Function Attrs: nofree norecurse nosync nounwind willreturn memory(none)
; CHECK-LABEL: define internal float @only_nofpclass_inf_return_users
; CHECK-SAME: (float [[ARG:%.*]]) #[[ATTR2]] {
; CHECK-NEXT:    [[ADD:%.*]] = fadd float [[ARG]], [[ARG]]
; CHECK-NEXT:    ret float [[ADD]]
;
  %add = fadd float %arg, %arg
  ret float %add
}

define float @call_noinf_return_0(float %arg) {
; TUNIT: Function Attrs: nofree norecurse nosync nounwind willreturn memory(none)
; TUNIT-LABEL: define nofpclass(inf) float @call_noinf_return_0
; TUNIT-SAME: (float [[ARG:%.*]]) #[[ATTR2]] {
; TUNIT-NEXT:    [[RESULT:%.*]] = call nofpclass(inf) float @only_nofpclass_inf_return_users(float [[ARG]]) #[[ATTR7]]
; TUNIT-NEXT:    ret float [[RESULT]]
;
; CGSCC: Function Attrs: nofree nosync nounwind willreturn memory(none)
; CGSCC-LABEL: define nofpclass(inf) float @call_noinf_return_0
; CGSCC-SAME: (float [[ARG:%.*]]) #[[ATTR3]] {
; CGSCC-NEXT:    [[RESULT:%.*]] = call nofpclass(inf) float @only_nofpclass_inf_return_users(float [[ARG]]) #[[ATTR6]]
; CGSCC-NEXT:    ret float [[RESULT]]
;
  %result = call nofpclass(inf) float @only_nofpclass_inf_return_users(float %arg)
  ret float %result
}

define float @call_noinf_return_1(float %arg) {
; TUNIT: Function Attrs: nofree norecurse nosync nounwind willreturn memory(none)
; TUNIT-LABEL: define nofpclass(inf) float @call_noinf_return_1
; TUNIT-SAME: (float [[ARG:%.*]]) #[[ATTR2]] {
; TUNIT-NEXT:    [[RESULT:%.*]] = call nofpclass(inf) float @only_nofpclass_inf_return_users(float [[ARG]]) #[[ATTR7]]
; TUNIT-NEXT:    ret float [[RESULT]]
;
; CGSCC: Function Attrs: nofree nosync nounwind willreturn memory(none)
; CGSCC-LABEL: define nofpclass(inf) float @call_noinf_return_1
; CGSCC-SAME: (float [[ARG:%.*]]) #[[ATTR3]] {
; CGSCC-NEXT:    [[RESULT:%.*]] = call nofpclass(inf) float @only_nofpclass_inf_return_users(float [[ARG]]) #[[ATTR6]]
; CGSCC-NEXT:    ret float [[RESULT]]
;
  %result = call nofpclass(inf) float @only_nofpclass_inf_return_users(float %arg)
  ret float %result
}

define float @fcmp_olt_assume_one_0_callsite_arg_return(float %arg) {
; CHECK-LABEL: define nofpclass(inf sub norm) float @fcmp_olt_assume_one_0_callsite_arg_return
; CHECK-SAME: (float returned nofpclass(inf sub norm) [[ARG:%.*]]) {
; CHECK-NEXT:  entry:
; CHECK-NEXT:    [[IS_NOT_ZERO_OR_NAN:%.*]] = fcmp one float [[ARG]], 0.000000e+00
; CHECK-NEXT:    call void @llvm.assume(i1 noundef [[IS_NOT_ZERO_OR_NAN]]) #[[ATTR6]]
; CHECK-NEXT:    call void @extern.use(float nofpclass(inf sub norm) [[ARG]])
; CHECK-NEXT:    ret float [[ARG]]
;
entry:
  %is.not.zero.or.nan = fcmp one float %arg, 0.0
  call void @llvm.assume(i1 %is.not.zero.or.nan)
  call void @extern.use(float %arg)
  ret float %arg
}

define float @fcmp_olt_assume_une_0_callsite_arg_return(float %arg) {
; CHECK-LABEL: define nofpclass(nan inf sub norm) float @fcmp_olt_assume_une_0_callsite_arg_return
; CHECK-SAME: (float returned nofpclass(nan inf sub norm) [[ARG:%.*]]) {
; CHECK-NEXT:  entry:
; CHECK-NEXT:    [[IS_NOT_ZERO_OR_NAN:%.*]] = fcmp une float [[ARG]], 0.000000e+00
; CHECK-NEXT:    call void @llvm.assume(i1 noundef [[IS_NOT_ZERO_OR_NAN]]) #[[ATTR6]]
; CHECK-NEXT:    call void @extern.use(float nofpclass(nan inf sub norm) [[ARG]])
; CHECK-NEXT:    ret float [[ARG]]
;
entry:
  %is.not.zero.or.nan = fcmp une float %arg, 0.0
  call void @llvm.assume(i1 %is.not.zero.or.nan)
  call void @extern.use(float %arg)
  ret float %arg
}

define half @fcmp_assume_issubnormal_callsite_arg_return(half %arg) {
; CHECK-LABEL: define nofpclass(zero sub) half @fcmp_assume_issubnormal_callsite_arg_return
; CHECK-SAME: (half returned nofpclass(zero sub) [[ARG:%.*]]) {
; CHECK-NEXT:  entry:
; CHECK-NEXT:    [[FABS:%.*]] = call half @llvm.fabs.f16(half nofpclass(zero sub) [[ARG]]) #[[ATTR6]]
; CHECK-NEXT:    [[IS_SUBNORMAL:%.*]] = fcmp olt half [[FABS]], 0xH0400
; CHECK-NEXT:    call void @llvm.assume(i1 noundef [[IS_SUBNORMAL]]) #[[ATTR6]]
; CHECK-NEXT:    call void @extern.use.f16(half nofpclass(zero sub) [[ARG]])
; CHECK-NEXT:    ret half [[ARG]]
;
entry:
  %fabs = call half @llvm.fabs.f16(half %arg)
  %is.subnormal = fcmp olt half %fabs, 0xH0400
  call void @llvm.assume(i1 %is.subnormal)
  call void @extern.use.f16(half %arg)
  ret half %arg
}

; Assume is after the call, shouldn't mark callsite.
define half @fcmp_assume_not_inf_after_call(half %arg) {
; CHECK-LABEL: define half @fcmp_assume_not_inf_after_call
; CHECK-SAME: (half returned [[ARG:%.*]]) {
; CHECK-NEXT:  entry:
; CHECK-NEXT:    call void @extern.use.f16(half [[ARG]])
; CHECK-NEXT:    [[NOT_INF:%.*]] = fcmp oeq half [[ARG]], 0xH7C00
; CHECK-NEXT:    call void @llvm.assume(i1 noundef [[NOT_INF]])
; CHECK-NEXT:    ret half [[ARG]]
;
entry:
  call void @extern.use.f16(half %arg)
  %not.inf = fcmp oeq half %arg, 0xH7C00
  call void @llvm.assume(i1 %not.inf)
  ret half %arg
}

; Assume not subnormal or zero, and not infinity
define half @fcmp_assume2_callsite_arg_return(half %arg) {
; CHECK-LABEL: define nofpclass(inf norm) half @fcmp_assume2_callsite_arg_return
; CHECK-SAME: (half returned nofpclass(inf norm) [[ARG:%.*]]) {
; CHECK-NEXT:  entry:
; CHECK-NEXT:    [[FABS:%.*]] = call nofpclass(inf) half @llvm.fabs.f16(half nofpclass(inf norm) [[ARG]]) #[[ATTR6]]
; CHECK-NEXT:    [[NOT_SUBNORMAL_OR_ZERO:%.*]] = fcmp oge half [[FABS]], 0xH0400
; CHECK-NEXT:    call void @llvm.assume(i1 noundef [[NOT_SUBNORMAL_OR_ZERO]]) #[[ATTR6]]
; CHECK-NEXT:    [[NOT_INF:%.*]] = fcmp oeq half [[ARG]], 0xH7C00
; CHECK-NEXT:    call void @llvm.assume(i1 noundef [[NOT_INF]]) #[[ATTR6]]
; CHECK-NEXT:    call void @extern.use.f16(half nofpclass(inf norm) [[ARG]])
; CHECK-NEXT:    ret half [[ARG]]
;
entry:
  %fabs = call half @llvm.fabs.f16(half %arg)
  %not.subnormal.or.zero = fcmp oge half %fabs, 0xH0400
  call void @llvm.assume(i1 %not.subnormal.or.zero)

  %not.inf = fcmp oeq half %arg, 0xH7C00
  call void @llvm.assume(i1 %not.inf)

  call void @extern.use.f16(half %arg)
  ret half %arg
}

define float @is_fpclass_assume_arg_return(float %arg) {
; CHECK-LABEL: define nofpclass(ninf nzero pnorm) float @is_fpclass_assume_arg_return
; CHECK-SAME: (float returned nofpclass(ninf nzero pnorm) [[ARG:%.*]]) {
; CHECK-NEXT:  entry:
; CHECK-NEXT:    [[CLASS_TEST:%.*]] = call i1 @llvm.is.fpclass.f32(float nofpclass(ninf nzero pnorm) [[ARG]], i32 noundef 292) #[[ATTR6]]
; CHECK-NEXT:    call void @llvm.assume(i1 noundef [[CLASS_TEST]]) #[[ATTR6]]
; CHECK-NEXT:    call void @extern.use(float nofpclass(ninf nzero pnorm) [[ARG]])
; CHECK-NEXT:    ret float [[ARG]]
;
entry:
  %class.test = call i1 @llvm.is.fpclass.f32(float %arg, i32 292)
  call void @llvm.assume(i1 %class.test)
  call void @extern.use(float %arg)
  ret float %arg
}

; Make sure we don't get confused by looking at an unrelated assume
; based on the fabs of the value.
define half @assume_fcmp_fabs_with_other_fabs_assume(half %arg) {
; CHECK-LABEL: define nofpclass(zero sub) half @assume_fcmp_fabs_with_other_fabs_assume
; CHECK-SAME: (half returned nofpclass(zero sub) [[ARG:%.*]]) {
; CHECK-NEXT:  entry:
; CHECK-NEXT:    [[FABS:%.*]] = call nofpclass(inf zero sub norm) half @llvm.fabs.f16(half nofpclass(zero sub) [[ARG]]) #[[ATTR6]]
; CHECK-NEXT:    [[UNRELATED_FABS:%.*]] = fcmp one half [[FABS]], 0xH0000
; CHECK-NEXT:    call void @llvm.assume(i1 noundef [[UNRELATED_FABS]]) #[[ATTR6]]
; CHECK-NEXT:    [[IS_SUBNORMAL:%.*]] = fcmp olt half [[FABS]], 0xH0400
; CHECK-NEXT:    call void @llvm.assume(i1 noundef [[IS_SUBNORMAL]]) #[[ATTR6]]
; CHECK-NEXT:    call void @extern.use.f16(half nofpclass(zero sub) [[ARG]])
; CHECK-NEXT:    call void @extern.use.f16(half nofpclass(inf zero sub norm) [[FABS]])
; CHECK-NEXT:    ret half [[ARG]]
;
entry:

  %fabs = call half @llvm.fabs.f16(half %arg)
  %unrelated.fabs = fcmp one half %fabs, 0.0
  call void @llvm.assume(i1 %unrelated.fabs)
  %is.subnormal = fcmp olt half %fabs, 0xH0400
  call void @llvm.assume(i1 %is.subnormal)
  call void @extern.use.f16(half %arg)
  call void @extern.use.f16(half %fabs)
  ret half %arg
}

; Make sure if looking through the fabs finds a different source
; value, we still identify a test mask by ignoring the fabs
define half @assume_fcmp_fabs_with_other_fabs_assume_fallback(half %arg) {
; CHECK-LABEL: define nofpclass(pinf zero sub) half @assume_fcmp_fabs_with_other_fabs_assume_fallback
; CHECK-SAME: (half returned nofpclass(pinf zero sub) [[ARG:%.*]]) {
; CHECK-NEXT:  entry:
; CHECK-NEXT:    [[FABS:%.*]] = call nofpclass(inf zero sub nnorm) half @llvm.fabs.f16(half nofpclass(pinf zero sub) [[ARG]]) #[[ATTR6]]
; CHECK-NEXT:    [[ONE_INF:%.*]] = fcmp oeq half [[ARG]], 0xH7C00
; CHECK-NEXT:    call void @llvm.assume(i1 noundef [[ONE_INF]]) #[[ATTR6]]
; CHECK-NEXT:    [[UNRELATED_FABS:%.*]] = fcmp oeq half [[FABS]], 0xH0000
; CHECK-NEXT:    call void @llvm.assume(i1 noundef [[UNRELATED_FABS]]) #[[ATTR6]]
; CHECK-NEXT:    [[IS_SUBNORMAL:%.*]] = fcmp olt half [[FABS]], 0xH0400
; CHECK-NEXT:    call void @llvm.assume(i1 noundef [[IS_SUBNORMAL]]) #[[ATTR6]]
; CHECK-NEXT:    call void @extern.use.f16(half nofpclass(pinf zero sub) [[ARG]])
; CHECK-NEXT:    call void @extern.use.f16(half nofpclass(inf zero sub nnorm) [[FABS]])
; CHECK-NEXT:    ret half [[ARG]]
;
entry:

  %fabs = call half @llvm.fabs.f16(half %arg)

  %one.inf = fcmp oeq half %arg, 0xH7C00
  call void @llvm.assume(i1 %one.inf)

  %unrelated.fabs = fcmp oeq half %fabs, 0.0
  call void @llvm.assume(i1 %unrelated.fabs)

  %is.subnormal = fcmp olt half %fabs, 0xH0400
  call void @llvm.assume(i1 %is.subnormal)
  call void @extern.use.f16(half %arg)
  call void @extern.use.f16(half %fabs)
  ret half %arg
}

define float @assume_bundles(i1 %c, float %ret) {
; CHECK-LABEL: define float @assume_bundles
; CHECK-SAME: (i1 noundef [[C:%.*]], float returned [[RET:%.*]]) {
; CHECK-NEXT:  entry:
; CHECK-NEXT:    br i1 [[C]], label [[A:%.*]], label [[B:%.*]]
; CHECK:       A:
; CHECK-NEXT:    call void @llvm.assume(i1 noundef true) #[[ATTR6]] [ "nofpclass"(float [[RET]], i32 3) ]
; CHECK-NEXT:    call void @extern.use(float nofpclass(nan) [[RET]])
; CHECK-NEXT:    ret float [[RET]]
; CHECK:       B:
; CHECK-NEXT:    call void @llvm.assume(i1 noundef true) [ "nofpclass"(float [[RET]], i32 12) ]
; CHECK-NEXT:    call void @extern.use(float nofpclass(ninf nnorm) [[RET]])
; CHECK-NEXT:    ret float [[RET]]
;
entry:
  br i1 %c, label %A, label %B

A:
  call void @llvm.assume(i1 true) [ "nofpclass"(float %ret, i32 3) ]
  call void @extern.use(float %ret)
  ret float %ret

B:
  call void @llvm.assume(i1 true) [ "nofpclass"(float %ret, i32 12) ]
  call void @extern.use(float %ret)
  ret float %ret
}

define float @returned_load(ptr %ptr) {
; CHECK: Function Attrs: nofree norecurse nosync nounwind willreturn memory(argmem: read)
; CHECK-LABEL: define float @returned_load
; CHECK-SAME: (ptr nocapture nofree noundef nonnull readonly align 4 dereferenceable(4) [[PTR:%.*]]) #[[ATTR4:[0-9]+]] {
; CHECK-NEXT:    [[LOAD:%.*]] = load float, ptr [[PTR]], align 4
; CHECK-NEXT:    ret float [[LOAD]]
;
  %load = load float, ptr %ptr
  ret float %load
}

define float @pass_nofpclass_inf_through_memory(float nofpclass(inf) %arg) {
; TUNIT: Function Attrs: nofree norecurse nosync nounwind willreturn memory(none)
; TUNIT-LABEL: define float @pass_nofpclass_inf_through_memory
; TUNIT-SAME: (float nofpclass(inf) [[ARG:%.*]]) #[[ATTR2]] {
; TUNIT-NEXT:    [[ALLOCA:%.*]] = alloca float, align 4
; TUNIT-NEXT:    store float [[ARG]], ptr [[ALLOCA]], align 4
; TUNIT-NEXT:    [[RET:%.*]] = call float @returned_load(ptr noalias nocapture nofree noundef nonnull readonly align 4 dereferenceable(4) [[ALLOCA]]) #[[ATTR7]]
; TUNIT-NEXT:    ret float [[RET]]
;
; CGSCC: Function Attrs: nofree nosync nounwind willreturn memory(none)
; CGSCC-LABEL: define float @pass_nofpclass_inf_through_memory
; CGSCC-SAME: (float nofpclass(inf) [[ARG:%.*]]) #[[ATTR3]] {
; CGSCC-NEXT:    [[ALLOCA:%.*]] = alloca float, align 4
; CGSCC-NEXT:    store float [[ARG]], ptr [[ALLOCA]], align 4
; CGSCC-NEXT:    [[RET:%.*]] = call float @returned_load(ptr noalias nocapture nofree noundef nonnull readonly align 4 dereferenceable(4) [[ALLOCA]]) #[[ATTR6]]
; CGSCC-NEXT:    ret float [[RET]]
;
  %alloca = alloca float
  store float %arg, ptr %alloca
  %ret = call float @returned_load(ptr %alloca)
  ret float %ret
}
